package org.springblade.common.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.appender.FormattedLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springblade.common.utils.JdbcUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * P6Spy自定义日志格式化器
 *
 * @author Jackey
 * @since 2024-11-15
 */
@Slf4j
@Component
public class P6SpyLogger extends FormattedLogger {

  /**
   * 默认慢SQL阈值：1000毫秒
   */
  private static final long SLOW_SQL_THRESHOLD = 2000L;

  /**
   * 飞书机器人密钥
   */
  private static final String SECRET = "fzmq1zDpi08XPJnWjXx7Bf";

  /**
   * 飞书机器人webhook地址
   */
  private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/49df47f7-1fc7-4a3d-9188-31c9d55b20af";

  /**
   * 当前环境
   */
  private static String activeProfile = "unknown";

  /**
   * 项目根目录路径
   */
  private static final String PROJECT_ROOT_PATH = System.getProperty("user.dir");

  /**
   * 作者信息缓存，避免重复读取
   */
  private static final HashMap<String, String> AUTHOR_CACHE = new HashMap<>();

  @Resource
  private Environment environment;

  @Resource
  private RequestMappingHandlerMapping handlerMapping;

  /**
   * 初始化当前环境
   */
  @PostConstruct
  public void init() {
    String[] profiles = environment.getActiveProfiles();
    if (profiles.length > 0) {
      activeProfile = profiles[0];
    }
    log.info("P6Spy初始化完成，当前环境：{}", activeProfile);
    log.info("项目根目录：{}", PROJECT_ROOT_PATH);

    // 检查handlerMapping是否正确注入
    if (handlerMapping != null) {
      log.info("RequestMappingHandlerMapping注入成功: {}", handlerMapping.getClass().getName());
    } else {
      log.warn("RequestMappingHandlerMapping注入失败，可能影响获取开发人员信息");
    }
  }

  @Override
  public void logException(Exception e) {
    log.error("SQL执行异常", e);
  }

  @Override
  public void logText(String text) {
    log.debug(text);
  }

  @Override
  public boolean isCategoryEnabled(Category category) {
    // 启用所有类别的日志记录
    return true;
  }

  @Override
  public void logSQL(int connectionId, String now, long elapsed, Category category, String prepared, String sql,
      String url) {
    if (StringUtils.isEmpty(sql)) {
      return;
    }

    // 只处理执行类型的SQL
    if (Category.STATEMENT.equals(category) || Category.BATCH.equals(category)) {
      String formattedSql = StringUtils.isNotEmpty(prepared) ? prepared : sql;

      // 记录所有SQL到调试日志
      log.debug("SQL: {} 执行时间: {}ms", formattedSql, elapsed);

      // 如果是慢SQL，进行告警处理
      if (elapsed >= SLOW_SQL_THRESHOLD) {
        handleSlowSql(formattedSql, elapsed);
      }
    }
  }

  /**
   * 处理慢SQL
   *
   * @param sql     SQL语句
   * @param elapsed 执行时间（毫秒）
   */
  private void handleSlowSql(String sql, long elapsed) {
    // 打印慢SQL日志
    log.warn("发现慢SQL - 执行时间: {}ms, SQL: {}", elapsed, sql);

    try {
      // 获取SQL指纹（去除具体参数值的SQL模板）
      String sqlFingerprint = getSqlFingerprint(sql);

      // // 检查是否已经记录过该SQL指纹，避免重复告警
      // if (JdbcUtils.isDuplication(sqlFingerprint)) {
      // log.info("该慢SQL已被记录，不重复告警: {}", sqlFingerprint);
      // return;
      // }

      // 发送飞书告警
      sendFeishuAlert(sql, elapsed);

	  JdbcUtils.insertSlowSqlAlert(activeProfile, elapsed, getCurrentRequestInfo(), sql);

      // 记录到数据库
      JdbcUtils.executeQueryAndInsert(sqlFingerprint, elapsed);

    } catch (Exception e) {
      log.error("处理慢SQL异常", e);
    }
  }

  /**
   * 获取SQL指纹
   * 简单实现：去除SQL中的具体数值和字符串
   *
   * @param sql 原始SQL
   * @return SQL指纹
   */
  private String getSqlFingerprint(String sql) {
    // 这里使用简单的正则替换，实际项目中可以使用更复杂的SQL解析器
    return sql.replaceAll("'[^']*'", "?") // 替换字符串
        .replaceAll("\\d+", "?") // 替换数字
        .replaceAll("\\s+", " ") // 规范化空白字符
        .trim();
  }

  /**
   * 获取当前请求的接口信息
   *
   * @return 接口信息，包含接口路径和开发人员
   */
  private String getCurrentRequestInfo() {
    try {
      ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
          .getRequestAttributes();
      if (requestAttributes == null) {
        log.warn("无法获取当前请求上下文，可能是在非HTTP请求线程中执行");
        return "未知接口（非HTTP上下文）";
      }

      HttpServletRequest request = requestAttributes.getRequest();
      if (request == null) {
        log.warn("无法获取当前请求对象");
        return "未知接口（请求对象为空）";
      }

      String requestURI = request.getRequestURI();
      log.info("当前请求URI: {}", requestURI);

      // 尝试获取处理当前请求的HandlerMethod
      Object handler = null;
      try {
        if (handlerMapping != null && handlerMapping.getHandler(request) != null) {
          handler = handlerMapping.getHandler(request).getHandler();
          log.info("成功获取请求处理器: {}", handler.getClass().getName());
        } else {
          log.warn("无法获取请求处理器，handlerMapping为空或getHandler返回null");
        }
      } catch (Exception e) {
        log.warn("获取请求处理器失败: {}", e.getMessage());
      }

      StringBuilder info = new StringBuilder();
      info.append("接口路径: ").append(requestURI != null ? requestURI : "未知");

      // 获取开发人员信息
      String author = "未知";
      if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Class<?> controllerClass = handlerMethod.getBeanType();
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        log.info("当前请求处理方法: {}.{}", controllerClass.getSimpleName(), methodName);

        // 构建缓存键，包含类名和方法名
        String cacheKey = controllerClass.getName() + "#" + methodName;

        // 尝试从缓存获取作者信息
        if (AUTHOR_CACHE.containsKey(cacheKey)) {
          author = AUTHOR_CACHE.get(cacheKey);
          log.info("从缓存中获取到方法级作者信息: {}", author);
        } else {
          // 1. 首先尝试从方法的JavaDoc注释中获取@author信息
          log.info("尝试从方法的JavaDoc注释中获取作者信息");
          String filePath = getControllerFilePath(controllerClass);
          if (StringUtils.isNotBlank(filePath)) {
            try {
              File file = new File(PROJECT_ROOT_PATH, filePath);
              if (file.exists()) {
                String content = new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())),
                    StandardCharsets.UTF_8);

                // 先尝试从方法JavaDoc中获取作者信息
                String methodAuthor = extractMethodJavaDocAuthor(content, methodName);
                if (StringUtils.isNotBlank(methodAuthor)) {
                  author = methodAuthor;
                  log.info("从方法JavaDoc获取到作者信息: {}", author);
                } else {
                  // 如果方法没有JavaDoc作者信息，再尝试从类JavaDoc获取
                  String classAuthor = extractClassJavaDocAuthor(content);
                  if (StringUtils.isNotBlank(classAuthor)) {
                    author = classAuthor;
                    log.info("从类JavaDoc获取到作者信息: {}", author);
                  } else {
                    // 最后尝试从任何位置获取作者信息
                    String anyAuthor = findAnyAuthorInFile(content);
                    if (StringUtils.isNotBlank(anyAuthor)) {
                      author = anyAuthor;
                      log.info("从文件任意位置获取到作者信息: {}", author);
                    }
                  }
                }
              } else {
                log.warn("文件不存在: {}", file.getAbsolutePath());
              }
            } catch (Exception e) {
              log.warn("读取文件内容失败: {}", e.getMessage());
            }
          }

          // 2. 如果JavaDoc获取失败，尝试从git获取最后修改者
          if ("未知".equals(author)) {
            log.info("从JavaDoc获取作者信息失败，尝试从git获取");
            if (StringUtils.isNotBlank(filePath)) {
              author = getLastCommitAuthor(filePath);
              log.info("从git获取到作者信息: {}", author);
            } else {
              log.warn("无法获取文件路径");
            }
          }

          // 3. 如果git获取失败，尝试从Swagger注解获取
          if ("未知".equals(author)) {
            log.info("从git获取作者信息失败，尝试从Swagger注解获取");
            try {
              // 先检查方法上的ApiOperation注解
              if (method.isAnnotationPresent(io.swagger.annotations.ApiOperation.class)) {
                io.swagger.annotations.ApiOperation apiOperation = method
                    .getAnnotation(io.swagger.annotations.ApiOperation.class);
                if (apiOperation != null) {
                  // 可以从notes或value中提取可能的作者信息
                  String notes = apiOperation.notes();
                  if (notes != null && notes.toLowerCase().contains("author")) {
                    author = notes;
                    log.info("从方法的Swagger注解获取到作者信息: {}", author);
                  }
                }
              }

              // 如果方法级别没有，再检查类级别
              if ("未知".equals(author) && controllerClass.isAnnotationPresent(io.swagger.annotations.Api.class)) {
                io.swagger.annotations.Api apiAnnotation = controllerClass
                    .getAnnotation(io.swagger.annotations.Api.class);
                if (apiAnnotation != null && apiAnnotation.tags().length > 0) {
                  author = apiAnnotation.tags()[0]; // 使用tag作为作者信息的替代
                  log.info("从Swagger注解获取到作者信息: {}", author);
                } else {
                  log.warn("Swagger注解没有tags或tags为空");
                }
              }
            } catch (Exception e) {
              log.warn("获取Swagger注解信息失败: {}", e.getMessage());
            }
          }

          // 4. 直接从类名或方法名中获取信息
          if ("未知".equals(author)) {
            log.info("尝试从类名或方法名中获取信息");
            // 如果类名中包含Controller，可能是某人的Controller
            String simpleName = controllerClass.getSimpleName();
            if (simpleName.contains("Controller") && !simpleName.equals("Controller")) {
              String possibleAuthor = simpleName.replace("Controller", "");
              if (StringUtils.isNotBlank(possibleAuthor)) {
                author = possibleAuthor;
                log.info("从类名中获取到可能的作者信息: {}", author);
              }
            }
          }

          // 将结果缓存
          AUTHOR_CACHE.put(cacheKey, author);
          log.info("将方法级作者信息缓存: {} -> {}", cacheKey, author);
        }
      } else {
        log.warn("处理器不是HandlerMethod类型: {}", handler != null ? handler.getClass().getName() : "null");
      }

      info.append("\n开发人员: ").append(author);

      return info.toString();
    } catch (Exception e) {
      log.error("获取请求信息失败: {}", e.getMessage(), e);
      return "未知接口（异常：" + e.getClass().getSimpleName() + "）";
    }
  }

  /**
   * 从文件内容中查找任何位置的@author标记
   *
   * @param fileContent 文件内容
   * @return 作者信息，如果未找到则返回null
   */
  private String findAnyAuthorInFile(String fileContent) {
    try {
      // 使用宽松的正则表达式匹配任何位置的@author标记
      Pattern pattern = Pattern.compile("@author\\s+([^\\n\\r]+)");
      Matcher matcher = pattern.matcher(fileContent);

      if (matcher.find()) {
        String author = matcher.group(1).trim();
        log.debug("在文件中找到@author标记: {}", author);
        return author;
      } else {
        log.debug("在文件中未找到任何@author标记");
      }

      return null;
    } catch (Exception e) {
      log.debug("查找@author标记失败: {}", e.getMessage());
      return null;
    }
  }

  /**
   * 从文件内容中提取类级别的JavaDoc @author信息
   *
   * @param fileContent 文件内容
   * @return 作者信息，如果未找到则返回null
   */
  private String extractClassJavaDocAuthor(String fileContent) {
    try {
      // 使用正则表达式匹配类级别的JavaDoc注释
      // 类级别JavaDoc通常位于文件开头，在package声明之前或之后，class声明之前
      Pattern pattern = Pattern.compile(
          "/\\*\\*\\s*(.*?)\\s*\\*/\\s*(?:public\\s+|private\\s+|protected\\s+)?(?:abstract\\s+)?class",
          Pattern.DOTALL);
      Matcher matcher = pattern.matcher(fileContent);

      if (matcher.find()) {
        String javaDoc = matcher.group(1);
        log.debug("找到类的JavaDoc注释: {}", javaDoc);

        // 从JavaDoc中提取@author标签
        Pattern authorPattern = Pattern.compile("@author\\s+([^\\n\\r]+)");
        Matcher authorMatcher = authorPattern.matcher(javaDoc);

        if (authorMatcher.find()) {
          String author = authorMatcher.group(1).trim();
          log.debug("从类的JavaDoc中找到@author: {}", author);
          return author;
        } else {
          log.debug("在类的JavaDoc中未找到@author标签");
        }
      } else {
        log.debug("未找到类的JavaDoc注释");
      }

      return null;
    } catch (Exception e) {
      log.debug("提取类JavaDoc作者信息失败: {}", e.getMessage());
      return null;
    }
  }

  /**
   * 从文件内容中提取指定方法的JavaDoc @author信息
   *
   * @param fileContent 文件内容
   * @param methodName  方法名
   * @return 作者信息，如果未找到则返回null
   */
  private String extractMethodJavaDocAuthor(String fileContent, String methodName) {
    try {
      // 使用更精确的正则表达式匹配方法的JavaDoc注释
      String regex = "/\\*\\*\\s*(.*?)\\s*\\*/\\s*[^\\n]*?\\s*" + methodName + "\\s*\\(";
      Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
      Matcher matcher = pattern.matcher(fileContent);

      if (matcher.find()) {
        String javaDoc = matcher.group(1);
        log.debug("找到方法 {} 的JavaDoc注释: {}", methodName, javaDoc);

        // 从JavaDoc中提取@author标签
        Pattern authorPattern = Pattern.compile("@author\\s+([^\\n\\r]+)");
        Matcher authorMatcher = authorPattern.matcher(javaDoc);

        if (authorMatcher.find()) {
          String author = authorMatcher.group(1).trim();
          log.debug("从方法 {} 的JavaDoc中找到@author: {}", methodName, author);
          return author;
        } else {
          log.debug("在方法 {} 的JavaDoc中未找到@author标签", methodName);
        }
      } else {
        log.debug("未找到方法 {} 的JavaDoc注释", methodName);
      }

      return null;
    } catch (Exception e) {
      log.debug("提取方法JavaDoc作者信息失败: {}", e.getMessage());
      return null;
    }
  }

  /**
   * 获取Controller类对应的文件路径
   *
   * @param controllerClass Controller类
   * @return 文件路径
   */
  private String getControllerFilePath(Class<?> controllerClass) {
    if (controllerClass == null) {
      return null;
    }

    String className = controllerClass.getSimpleName();
    String packageName = controllerClass.getPackage().getName();

    // 将包名转换为目录路径
    String packagePath = packageName.replace('.', '/');

    // 构建相对于项目根目录的文件路径
    return "src/main/java/" + packagePath + "/" + className + ".java";
  }

  /**
   * 获取文件最后提交的作者
   *
   * @param filePath 文件路径
   * @return 作者名称
   */
  private String getLastCommitAuthor(String filePath) {
    try {
      // 构建git命令
      String command = "git log -1 --pretty=format:%an " + filePath;
      log.info("执行git命令: {}", command);

      // 在项目根目录执行命令
      ProcessBuilder processBuilder = new ProcessBuilder();
      processBuilder.command("bash", "-c", command);
      processBuilder.directory(new File(PROJECT_ROOT_PATH));
      processBuilder.redirectErrorStream(true);

      // 执行命令
      Process process = processBuilder.start();

      // 读取命令输出
      BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
      StringBuilder output = new StringBuilder();
      String line;
      while ((line = reader.readLine()) != null) {
        output.append(line);
      }

      // 等待命令执行完成
      int exitCode = process.waitFor();

      String result = output.toString().trim();
      log.info("Git命令执行结果: 退出码={}, 输出={}", exitCode, result);

      if (exitCode == 0 && StringUtils.isNotBlank(result)) {
        return result;
      } else {
        // 尝试使用另一种命令格式
        command = "cd " + PROJECT_ROOT_PATH + " && git log -1 --pretty=format:%an " + filePath;
        log.info("尝试另一种命令格式: {}", command);

        Process process2 = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });
        reader = new BufferedReader(new InputStreamReader(process2.getInputStream()));
        output = new StringBuilder();
        while ((line = reader.readLine()) != null) {
          output.append(line);
        }

        exitCode = process2.waitFor();
        result = output.toString().trim();
        log.info("第二次Git命令执行结果: 退出码={}, 输出={}", exitCode, result);

        if (exitCode == 0 && StringUtils.isNotBlank(result)) {
          return result;
        }

        log.warn("获取git作者信息失败，退出码: {}, 输出: {}", exitCode, result);
        return "未知";
      }
    } catch (IOException | InterruptedException e) {
      log.error("执行git命令异常: {}", e.getMessage(), e);
      return "未知";
    }
  }

  /**
   * 发送飞书告警
   *
   * @param sql     SQL语句
   * @param elapsed 执行时间（毫秒）
   */
  private void sendFeishuAlert(String sql, long elapsed) {
    try {
      // 定义 webhook 的 URL 地址
      URL targetUrl = new URL(WEBHOOK_URL);
      log.info("准备发送飞书告警，目标URL: {}", WEBHOOK_URL);

      // 创建一个 HTTP POST 请求
      HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
      connection.setRequestMethod("POST");
      connection.setDoOutput(true);

      // 设置请求头
      connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");

      // 获取当前请求的接口信息
      String apiPath = "未知路径";

      try {
        // 获取请求上下文
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
            .getRequestAttributes();
        if (requestAttributes != null) {
          HttpServletRequest request = requestAttributes.getRequest();
          if (request != null) {
            apiPath = request.getRequestURI();
          }
        }
      } catch (Exception e) {
        log.error("获取请求URI失败: {}", e.getMessage());
      }

      // 构建简化的请求信息
      String requestInfo = "【接口路径】" + apiPath;
      log.info("构建的请求信息: {}", requestInfo);

      // 设置请求体
      HashMap<String, String> contentMap = new HashMap<>(2);
      // 添加环境信息到告警消息中
      String envInfo = StringUtils.isNotBlank(activeProfile) ? "【管理端 " + activeProfile.toUpperCase() + "】" : "【未知环境】";

      // 构建更加清晰的告警消息
      StringBuilder messageBuilder = new StringBuilder();
      messageBuilder.append(envInfo).append(" 慢SQL告警\n\n");
      messageBuilder.append("【执行时间】").append(elapsed).append(" ms\n\n");
      messageBuilder.append(requestInfo).append("\n\n");
      messageBuilder.append("【SQL语句】\n").append(sql);

      String message = messageBuilder.toString();
      contentMap.put("text", message);

      JSONObject jsonObject = new JSONObject();
      jsonObject.put("msg_type", "text");
      jsonObject.put("content", contentMap);

      // 签名信息
      int timestamp = (int) (System.currentTimeMillis() / 1000);
      String sign = genSign(SECRET, timestamp);
      jsonObject.put("timestamp", timestamp);
      jsonObject.put("sign", sign);

      // 将 JSON 对象转换为字符串
      String payload = JSON.toJSONString(jsonObject);
      log.info("飞书告警消息内容: {}", payload);

      connection.getOutputStream().write(payload.getBytes());

      // 获取响应状态码
      int statusCode = connection.getResponseCode();
      log.info("飞书告警发送状态码: {}", statusCode);

      // 读取响应内容
      BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
      StringBuilder response = new StringBuilder();
      String line;
      while ((line = reader.readLine()) != null) {
        response.append(line);
      }
      reader.close();

      log.info("飞书告警响应内容: {}", response.toString());

    } catch (Exception e) {
      log.error("发送飞书告警失败: {}", e.getMessage(), e);
    }
  }

  /**
   * 生成飞书签名
   *
   * @param secret    密钥
   * @param timestamp 时间戳
   * @return 签名
   */
  private static String genSign(String secret, int timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
    // 把timestamp+"\n"+密钥当做签名字符串
    String stringToSign = timestamp + "\n" + secret;
    // 使用HmacSHA256算法计算签名
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] signData = mac.doFinal(new byte[] {});
    return new String(Base64.encodeBase64(signData));
  }
}
